package com.os.service.impl;


import com.os.content.MemoryContent;
import com.os.content.SwapContent;
import com.os.pojo.Memory;
import com.os.pojo.PCB;
import com.os.pojo.physicalmemory.Frame;
import com.os.pojo.virtualmemory.*;
import com.os.pojo.vo.*;
import com.os.service.OsService;
import com.os.service.SwapService;
import com.os.utils.AlgorithmUtils;
import com.os.utils.FileUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;


/**
 * 表示操作系统
 */

public class OsServiceImpl implements OsService {


    /**
     * 进程列表
     */
    private List<PCB> pcbList;

    /**
     * 代表系统物理内存
     */
    private Memory<Frame> physicalMemory;

    /**
     * 当前运行中的段列表，代表物理内存哪些块被使用
     */
    private List<Segment> segmentList;

    /**
     * 当前正在执行的进程
     */
    private PCB nowPcb = null;

    /**
     * 段的置换服务
     */
    private SwapService<Segment> swapService;


    /**
     * 显示读取过程信息
     */
    private StringBuilder processInfo;

    /**
     * 当前访问列表
     */
    private List<ActionVo> toActionList;

    /**
     * 一次访问过程
     */
    private ActionVo actionVo;

    @Override
    public void changeSwapAlgorithm(String swap) {
        swapService.setSwapName(swap);
        for (PCB pcb : pcbList) {
            for (Segment segment : pcb.segmentTable.segments) {
                segment.pageSwapService.setSwapName(swapService.getSwapName());
            }
        }
    }

    @Override
    public String getSwapAlgorithm() {
        return swapService.getSwapName();
    }

    /**
     * 初始化各个数据结构，例如PCB,内存空间
     */
    public OsServiceImpl() {
        MemoryContent.reload();
        processInfo = new StringBuilder();
        toActionList = new ArrayList<>();
        reloadConfig();
    }

    /**
     * 请求页块
     */
    private Page requestPage(Segment segment, int pageId, int len) {

        // 获取块号
        int frameId = -1;
        if (segment.startAdress != -1) {
            frameId = segment.startAdress / MemoryContent.FRAME_SIZE + pageId;
        }

        return new Page(segment.id, pageId, segment.startAdress != -1, frameId, len, null);
    }

    /**
     * 获取页表
     */
    private PageTable getPageTable(Segment segment, String text) {
        PageTable pageTable = new PageTable(text.length());

        BufferedReader reader = new BufferedReader(new StringReader(text));

        int id = 0;
        try {
            while (true) {

                char[] temp = new char[MemoryContent.FRAME_SIZE];
                int len = reader.read(temp);
                if (len == -1) break;
                // 获取空闲页块
                Page page = requestPage(segment, id, len);
                page.chars = temp;
                pageTable.pages[id++] = page;

            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return pageTable;
    }

    private String showGetSegmentList() {
        StringBuilder ss = new StringBuilder();
        ss.append("\n---------------------------\n");
        ss.append("当前物理内存中段分布情况：\n");

        for (Segment segment : segmentList) {
            ss.append(String.format("进程ID：%s 段号：%s 开始地址：%s 大小：%s\n", segment.pcbId, segment.id,
                    segment.startAdress, segment.size));
        }
        ss.append("---------------------------\n");
        return ss.toString();
    }

    /**
     * 获取能容下此段的空闲空间的开始地址,没有返回-1
     */
    private int getFreeSpaceOfSegment(int size) {
        processInfo.append(String.format("寻找能存下段大小为 %s 的空闲空间\n", size));

        if (segmentList.isEmpty()) {
            processInfo.append("当前物理内存没有进程加载，开始地址为0\n");
            return 0;
        }
        processInfo.append(showGetSegmentList());

        int startAddress = 0;
        for (int i = 0; i < segmentList.size(); i++) {
            Segment segment = segmentList.get(i);
            if (segment.startAdress - startAddress >= size) {
                processInfo.append(String.format("得到开始地址：%s\n", startAddress));
                return startAddress;
            }

            // 计算下一个开始地址，必须是内存块大小的整数倍
            startAddress = AlgorithmUtils.countMemorySize(segment.startAdress + segment.size);
        }
        // 判断是否为末尾的开始地址
        if (startAddress + size <= MemoryContent.PHYSICAL_MEMORY) {
            processInfo.append(String.format("得到开始地址：%s\n", startAddress));
            return startAddress;
        }
        processInfo.append("获取失败，没有符合的空闲空间\n");
        return -1;
    }


    private String showPageList(Segment segment) {

        StringBuilder ss = new StringBuilder();
        ss.append("\n---------------------------\n");
        ss.append("当前段内存中内存块分布情况\n");
        int maxSize = (segment.startAdress + segment.size) / MemoryContent.FRAME_SIZE;
        for (int i = segment.startAdress / MemoryContent.FRAME_SIZE; i < maxSize; i++) {
            Frame frame = physicalMemory.memoryBlock[i];
            ss.append(String.format("块号：%s 页号：%s 内容：%s\n", frame.id, frame.pageId,
                    AlgorithmUtils.getPageChats(frame.chars, true)));
        }
        ss.append("---------------------------\n");
        return ss.toString();
    }

    /**
     * 查找段中能存储页的空闲分区
     */
    private int getFreeSpaceOfPage(Segment segment) {
        processInfo.append("查找空闲的内存块\n");
        processInfo.append(showPageList(segment));
        int maxSize = (segment.startAdress + segment.size) / MemoryContent.FRAME_SIZE;
        for (int i = segment.startAdress / MemoryContent.FRAME_SIZE; i < maxSize; i++) {
            Frame frame = physicalMemory.memoryBlock[i];
            if (frame.pageId == -1) {
                processInfo.append(String.format("分配得到内存块号为：%s\n", frame.id));
                return i;
            }
        }
        processInfo.append("分配失败，没有空闲的内存块\n");
        return -1;
    }

    /**
     * 将可以段移除，并返回段索引
     */
    private int getSegmentIndex() {
        Segment segment = swapService.get(actionVo);

        return segmentList.indexOf(segment);
    }


    /**
     * 根据开始地址，插入内存段列表
     */
    private void insertSegmentList(int startAddress, Segment segment) {
        int i;
        // 根据开始地址，插入内存段列表
        for (i = 0; i < segmentList.size(); i++) {
            // 当某段开始地址大于开始地址，则说明已经找到了位置
            if (segmentList.get(i).startAdress > startAddress) {
                break;
            }
        }
        // 插入i
        segmentList.add(i, segment);

    }

    /**
     * 从内存中将此段移除,
     * 并返回该开始地址
     */
    private int removeSegment(int segIndex) {
        Segment segment = segmentList.get(segIndex);
        segment.isUsed = false;
        segmentList.remove(segIndex);
        // 同样移除下面页表 全部置isUsed为false
        Arrays.stream(segment.pageTable.pages).forEach(this::removePage);
        // 清空驻留集
        segment.pageSwapService.clearLinkedList();

        return segment.startAdress;
    }

    /**
     * 将段加入物理内存
     */
    private boolean insertSegmentToMemory(int segmentId) {
        PCB pcb = nowPcb;
        Segment segment = pcb.segmentTable.segments[segmentId];

        // 计算实际存储的大小，内存块的整数倍
        int memeorySize = segment.size;


        // 根据段列表查看物理内存中是否有容得下该段的空闲空间
        // 如果没有则根据置换算法切换几个出去，不能置换当前PCB正在使用的段
        int startAddress = -1;
        while ((startAddress = getFreeSpaceOfSegment(memeorySize)) == -1 && swapService.len() > 0) {
            int segIndex = getSegmentIndex();
            if (segIndex != -1) {
                // 移除该段，再返回 startAddress
                removeSegment(segIndex);
            } else {
//                return false;
            }
        }
        if (startAddress == -1) return false;

        segment.isUsed = true;
        segment.startAdress = startAddress;

        // 加入置换队列
        swapService.add(segment);
        // 插入内存段列表
        insertSegmentList(startAddress, segment);

        return true;
    }

    /**
     * 移除页
     */
    private void removePage(Page page) {

        if (page.isUsed) {
            if (page.frameId == -1)
                System.out.println(page.id + " frameId == -1");
            Frame frame = physicalMemory.memoryBlock[page.frameId];
            frame.chars = null;
            frame.pageId = -1;
            frame.pcbId = -1;
            frame.segmentId = -1;
        }
        page.isUsed = false;
        page.LRUTime = 0;
        page.FIFOTime = 0;
        page.CLOCKBit = false;
    }

    /**
     * 将页加入物理内存
     */
    private boolean insertPageToMemory(int segmentId, int pageId) {
        PCB pcb = nowPcb;
        Segment segment = pcb.segmentTable.segments[segmentId];
        if (segment.isUsed) {
            // 代表段表命中
            swapService.onFocus(segment, actionVo);
        } else {
            processInfo.append(String.format("段号 %s 未命中\n", segmentId));
            if (!insertSegmentToMemory(segmentId)) {
                return false;
            }
            processInfo.append(showGetSegmentList());
        }

        processInfo.append("之后在段内存中再分配页\n");
        // 为页分配段内存
        PageTable pageTable = segment.pageTable;

        SwapService<Page> pageSwapService = segment.pageSwapService;
        Page page = pageTable.pages[pageId];

        int frameId = getFreeSpaceOfPage(segment);
        Page pageRemove = null;
        if (frameId == -1) {
            pageRemove = pageSwapService.get(actionVo);
            if (pageRemove != null) {
                frameId = pageRemove.frameId;
                removePage(pageRemove);
            }
        }

        if (frameId == -1) return false;
        page.frameId = frameId;
        page.isUsed = true;


        Frame frame = physicalMemory.memoryBlock[frameId];
        frame.chars = page.chars;
        frame.pageId = page.id;
        frame.pcbId = nowPcb.id;
        frame.segmentId = segmentId;
        pageSwapService.add(page);

        return true;
    }

    /**
     * 请求段
     * 段内包含一段连续的内存
     *
     * @param pcbId
     * @param segmentId
     * @return
     */
    private Segment requestSegment(int pcbId, int segmentId, int size, String text) {
        // 分配空间给Segment 填充页表PageTable
        // 段大小不能超过物理内存,这里设置段大小为物理内存的1/2,
        Segment segment = new Segment(pcbId, segmentId, Math.min(AlgorithmUtils.countMemorySize(size), MemoryContent.PAGE_MAX_NUMBER), size);
        try {
            // 给段绑定页置换方法，与段的置换算法一样
            segment.pageSwapService = new SwapServiceImpl<>(processInfo, new Page());
            segment.pageSwapService.setSwapName(swapService.getSwapName());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        segment.pageTable = getPageTable(segment, text);
        return segment;
    }


    /**
     * 创建PCB
     *
     * @param segmentTable 段表
     * @param segs         每个段的内容
     */
    private PCB createPcb(int pcbId, String fileName, SegmentTable segmentTable, String[] segs, int realLen) {
        // 创建进程
        PCB pcb = new PCB(pcbId, fileName, realLen, segmentTable);
        nowPcb = pcb;
        // 分段加载
        for (int i = 0; i < segs.length; i++) {
            String segText = segs[i];

            // 当前段表
            Segment segment = requestSegment(pcbId, i, segText.length(), segText);
            segmentTable.segments[i] = segment;
        }

        return pcb;
    }

    /**
     * 清空物理内存
     */
    public void clearPhysicalMemory() {
        toActionList.clear();
        segmentList = new LinkedList<>();
        swapService = new SwapServiceImpl<>(processInfo, new Segment());
        physicalMemory = new Memory<>(MemoryContent.PHYSICAL_MEMORY, Frame.class, MemoryContent.FRAME_SIZE);
        for (int i = 0; i < physicalMemory.memoryBlock.length; i++) {
            physicalMemory.memoryBlock[i] = new Frame(i);
        }
    }

    public void reloadConfig() {
        pcbList = new LinkedList<>();
        clearPhysicalMemory();
    }


    private String[] splitSegment(String[] segs) {
        List<String> list = new ArrayList<>();
        // 段的实际大小最大为MemoryContent.PAGE_MAX_NUMBER * MemoryContent.FRAME_SIZE,
        // 而段的内存占用大小只有MemoryContent.PAGE_MAX_NUMBER
        int maxSize = MemoryContent.PAGE_MAX_NUMBER * MemoryContent.FRAME_SIZE;
        for (int i = 0; i < segs.length; i++) {
            StringBuilder ss = new StringBuilder(segs[i]);

            do {
                int end = Math.min(ss.length(), maxSize);
                list.add(ss.substring(0, end));
                ss = ss.delete(0, end);

            } while (ss.length() > 0);

        }
        return list.toArray(new String[0]);
    }

    /**
     * 打开文本文件，按照 分隔符 分割一个段,每 X 个字节分成一页
     *
     * @param path
     */
    @Override
    public void open(String path) {
        if (path.charAt(0) == '/') {
            path = path.substring(1);
        }
        // 获取文件名
        String fileName = Paths.get(path).getFileName().toString();
        // 获取文本内容，然后分割成几个段
        String fileText = FileUtils.getFileText(path);
        String[] segs = fileText.split(Pattern.quote(MemoryContent.SEGMENT_SPLIT));

        // 检查每个段实际大小，是否大于最大页号*块大小，大于就再次分割
        segs = splitSegment(segs);

        // 创建段表
        SegmentTable segmentTable = new SegmentTable(segs.length);

        int pcbId = pcbList.size();

        // 创建进程
        PCB pcb = createPcb(pcbId, fileName, segmentTable, segs, fileText.length());

        pcbList.add(pcb);
    }


    /**
     * 地址转换
     * 通过段号，页号，偏移找到物理地址
     */
    public int addressConvert(PCB pcb, int segmentId, int pageId) throws Exception {
        Segment segment = pcb.segmentTable.segments[segmentId];
        Page page = segment.pageTable.pages[pageId];

        // 增加请求次数
        segment.pageSwapService.increaseRequestNum();
        swapService.increaseRequestNum();
        if (page.isUsed) {
            // 代表段表页命中
            swapService.onFocus(segment, actionVo);
            // 代表页表命中
            segment.pageSwapService.onFocus(page, actionVo);
        } else {
            processInfo.append(String.format("页号 %s 未命中\n", pageId));
            if (!insertPageToMemory(segmentId, pageId)) {
                throw new Exception("内存分配失败");
            }
            processInfo.append(showPageList(segment));
        }
        // 获取内存块号
        return page.frameId;
    }


    /**
     * 切换运行另一个pcb
     */
    public void changeNowPcb(int pcbId) {
        if (pcbId >= pcbList.size()) {
            throw new ArrayIndexOutOfBoundsException("进程id超出范围");
        }
        nowPcb = pcbList.get(pcbId);
    }

    @Override
    public void changeMemoryInfo(int physicalMemorySize, int frameSize) {

        MemoryContent.PHYSICAL_MEMORY = physicalMemorySize;
        MemoryContent.FRAME_SIZE = frameSize;
        MemoryContent.PAGE_MAX_NUMBER = physicalMemorySize / 2;
        reloadConfig();
    }

    @Override
    public MemoryInfoVo getMemoryInfo() {
        return new MemoryInfoVo(MemoryContent.PHYSICAL_MEMORY, MemoryContent.FRAME_SIZE);
    }

    @Override
    public List<PcbInfoVo> getPcbInfoList() {
        List<PcbInfoVo> list = new ArrayList<>();

        pcbList.forEach((x) -> {
            PcbInfoVo pcbInfoVo = new PcbInfoVo(x.id, x.name, x.len);
            list.add(pcbInfoVo);
        });
        return list;
    }


    @Override
    public List<SegmentVo> getSegmentInfoByPcbId(int pcbId) {
        List<SegmentVo> list = new ArrayList<>();

        // 获取进程的段数组
        Segment[] segments = pcbList.get(pcbId).segmentTable.segments;
        // 循环依次加入信息到list中
        for (Segment segment : segments) {
            // 将segment的信息去敏
            SegmentVo s = new SegmentVo(segment.pcbId, segment.id,
                    segment.startAdress,
                    segment.size, segment.len, segment.isUsed, segment.LRUTime,
                    segment.FIFOTime, segment.CLOCKBit, null);

            // 加载页列表
            List<PageVo> pageVoList = new ArrayList<>();
            s.setPageVoList(pageVoList);
            // 再依次将page的信息去敏
            for (Page page : segment.pageTable.pages) {
                PageVo p = new PageVo(segment.id, page.id, page.frameId, page.len,
                        AlgorithmUtils.getPageChats(page.chars, true),
                        page.isUsed, page.LRUTime, page.FIFOTime, page.CLOCKBit);
                pageVoList.add(p);
            }
            list.add(s);
        }
        return list;
    }


    @Override
    public List<FrameVo> getMemoryFrameList() {
        List<FrameVo> list = new ArrayList<>();
        if (physicalMemory.memoryBlock == null)
            return list;
        for (Frame frame : physicalMemory.memoryBlock) {
            String pcbName = "";
            if (frame.pcbId != -1 && frame.pcbId < pcbList.size()) {
                pcbName = pcbList.get(frame.pcbId).name;
            }
            FrameVo frameVo = new FrameVo(frame.id, frame.pcbId, pcbName, frame.segmentId, frame.pageId,
                    AlgorithmUtils.getPageChats(frame.chars, true));

            list.add(frameVo);
        }

        return list;
    }


    private void getAddressAllSeg(List<Integer> addressList) {
        for (int i = 0; i < nowPcb.segmentTable.segments.length; i++) {
            getAddressAllPage(addressList, i);
        }

    }


    private void getAddressAllPage(List<Integer> addressList, int segmentId) {
        for (int j = 0; j < nowPcb.segmentTable.segments[segmentId].pageTable.pages.length; j++) {
            getAddressAllOffset(addressList, segmentId, j);
        }
    }

    private void getAddressAllOffset(List<Integer> addressList, int segmentId, int pageId) {

        for (int k = 0; k < nowPcb.segmentTable.segments[segmentId].pageTable.pages[pageId].len; k++) {
            addressList.add(AlgorithmUtils.idToAddress(segmentId, pageId, k));
        }
    }


    @Override
    public List<Integer> getLogicAddress(int pcbId, Integer segmentId, Integer pageId, Integer offset) {
        changeNowPcb(pcbId);

        List<Integer> addressList = new ArrayList<>();

        if (segmentId == null) {
            getAddressAllSeg(addressList);
        } else if (pageId == null) {
            getAddressAllPage(addressList, segmentId);
        } else if (offset == null) {
            getAddressAllOffset(addressList, segmentId, pageId);
        } else {
            addressList.add(AlgorithmUtils.idToAddress(segmentId, pageId, offset));
        }

        return addressList;
    }


    public String getValueByAddress(int pcbId, int address) throws Exception {
        AddressVo addressVo = AlgorithmUtils.splitAddress(address);
        // 设置actionVo
        actionVo.setSegmentId(addressVo.getSegmentId());
        actionVo.setPageId(addressVo.getPageId());
        actionVo.setOffset(addressVo.getOffset());

        // 添加进程信息
        processInfo.append(String.format("逻辑地址：%s\n", address))
                .append(String.format("段号：%s 页号：%s 偏移量：%s\n",
                        addressVo.getSegmentId(), addressVo.getPageId(), addressVo.getOffset()));
        processInfo.append("地址转换得到如下：\n");
        // 记录当前位置，之后在这里插入错误信息
        int insertOffset = processInfo.length();
        // 修改当前进程
        changeNowPcb(pcbId);
        // 检查越界
        AlgorithmUtils.checkAddress(nowPcb, addressVo);


        int frameId = addressConvert(nowPcb, addressVo.getSegmentId(), addressVo.getPageId());

        char c = physicalMemory.memoryBlock[frameId].chars[addressVo.getOffset()];
        processInfo.append("---------------------------\n\n");
        String msg = AlgorithmUtils.getChat(c, true);
        actionVo.setMsg(msg);
        actionVo.setPhysicalAddress(AlgorithmUtils.idToPhyscialAddress(frameId, addressVo.getOffset()));
        processInfo.insert(insertOffset, String.format(String.format("块号：%s 偏移量：%s 内容为：%s\n---------------------------\n\n", frameId, addressVo.getOffset(), msg)));
        return msg;
    }

    @Override
    public void initMemory() {
        clearPhysicalMemory();
        // 将进程中每一个段页都进行初始化
        for (PCB pcb : pcbList) {
            for (Segment segment : pcb.segmentTable.segments) {
                segment.isUsed = false;
                segment.CLOCKBit = false;
                segment.FIFOTime = 0;
                segment.LRUTime = 0;
                segment.pageSwapService = new SwapServiceImpl<>(processInfo, new Page());
                for (Page page : segment.pageTable.pages) {
                    page.isUsed = false;
                    page.frameId = -1;
                    page.CLOCKBit = false;
                    page.FIFOTime = 0;
                    page.LRUTime = 0;
                }
            }
        }
    }

    /**
     * 计算当前算法的命中率与缺页率
     */
    private String countFocusGate() {
        StringBuilder ss = new StringBuilder();
        ss.append("---------------------------\n");

        double segmentRate = ((double) swapService.getFocusNum() / swapService.getRequestNum()) * 100;
        double pageRate = 0;
        int pageRequestNum = 0, pageFocusNum = 0;
        for (Segment segment : nowPcb.segmentTable.segments) {
            pageFocusNum += segment.pageSwapService.getFocusNum();
            pageRequestNum += segment.pageSwapService.getRequestNum();
        }

        pageRate = ((double) pageFocusNum / pageRequestNum) * 100;

        ss.append(String.format("访问次数：%s 置换算法：%s\n", swapService.getRequestNum(), swapService.getSwapName()));
        ss.append(String.format("段的命中次数：%s 命中率：%.2f%%\n", swapService.getFocusNum(), segmentRate));
        ss.append(String.format("页的命中次数：%s 命中率：%.2f%%\n", pageFocusNum, pageRate));
        ss.append("---------------------------\n");
        return ss.toString();
    }


    /**
     * 为OPT算法设置访问队列
     */
    private void setOPTList(List<Integer> addressList) {

        for (int address : addressList) {
            AddressVo addressVo = AlgorithmUtils.splitAddress(address);
            // 检查越界
            AlgorithmUtils.checkAddress(nowPcb, addressVo);

            // 处理段
            Segment segment = nowPcb.segmentTable.segments[addressVo.getSegmentId()];

            // 处理页
            swapService.addToOPTList(segment);

            segment.pageSwapService.addToOPTList(segment.pageTable.pages[addressVo.getPageId()]);
        }

    }

    /**
     * 使用四种算法读取，并计算命中率缺页率
     */
    @Override
    public String countAlgorithm(int pcbId, List<Integer> addressList) {
        StringBuilder ss = new StringBuilder();


        String[] algorithms = new String[]{SwapContent.OPT, SwapContent.FIFO, SwapContent.LRU, SwapContent.CLOCK};

        for (String al : algorithms) {
            initMemory();
            changeSwapAlgorithm(al);
            // 如果是OPT，则需要设置访问队列
            if (al.equals(SwapContent.OPT)) {
                setOPTList(addressList);
            }

            getValueByAddressList(pcbId, addressList);

            ss.append(countFocusGate()).append("\n");
        }
        initMemory();
        return ss.toString();
    }

    @Override
    public String getValueByAddressList(int pcbId, List<Integer> addressList) {
        StringBuilder ss = new StringBuilder("查询结果为：\n");
        StringBuilder error = new StringBuilder();
        ss.append("---------------------------\n");
        error.append("---------------------------\n");
        int i = 0;
        for (int address : addressList) {
            actionVo = new ActionVo(toActionList.size());
            toActionList.add(actionVo);
            actionVo.setLogicAddress(address);
            actionVo.setSwapAlgorithm(swapService.getSwapName());
            try {
                ss.append(getValueByAddress(pcbId, address));
                i++;
                if (i % MemoryContent.LINE_LENGTH == 0) {
                    ss.append("\n");
                    i = 0;
                }

            } catch (Exception e) {
                error.append(String.format("地址：%s  %s\n", address, e.getMessage()));
                actionVo.setMsg("出错");
            }
        }
        ss.append("\n---------------------------\n");

        ss.append(countFocusGate());

        return ss.append(error).append(getProcessInfo()).toString();
    }

    private String getProcessInfo() {
        String t = processInfo.toString();
        processInfo.setLength(0);
        return t;
    }


    @Override
    public void shuttleList(List<Integer> addressList) {
        Collections.shuffle(addressList);
    }

    @Override
    public List<ActionVo> getActionList() {
        return new ArrayList<>(toActionList);
    }
}
